接續著上一篇的, 繼續努力
把這邊再練習熟悉就可以去考試了
先再複習上次的imdb
#load tfds data
imdb, info = tfds.load("imdb_reviews", with_info=True, as_supervised=True)
train_data, test_data = imdb['train'], imdb['test']
training_sentences = []
training_labels = []
testing_sentences = []
testing_labels = []
#s,l都是儲存成tf.Tensor
#透過.numpy()轉換成numpy
for s,l in train_data:
training_sentences.append(str(s.numpy()))
training_labels.append(l.numpy())
for s, l in test_data:
testing_sentences.append(str(s.numpy()))
testing_labels.append(l.numpy())
training_labels_final = np.array(training_labels)
testing_labels_final = np.array(testing_labels)
要把文字送進去model訓練有兩個問題
- 要把文字數字化
- input要相同長度
接下來就是今天的重頭戲, 首先是Tokenizer
sentences = [
'I love my dog',
'I love my cat'
]
#將文字變成數字, token化
#加標點符號不影響, 不會因為dog!, 就增加一個index
#透過空格或, 分割語句
#不分大小寫 I = i
#tokenizer會依照出現的頻率來排序, 越前面的出現頻率越高
tokenizer = Tokenizer(num_words = 100)
#根據fit_on_texts自動去tokenize, 會自動定義出token需要的長度有多少
tokenizer.fit_on_texts(sentences)
#word_index 是詞對照到數字的dict
word_index = tokenizer.word_index
print(word_index)
#{'i': 1, 'my' : 3, 'dog' : 4, 'cat' : 5, 'love' : 2}
#建立好token後, 對想轉換的句子做texts_to_sequences
sentences = [
'I love my dog',
'I love my cat'
'You love my dog!',
'Do you think my dog is amazing?'
]
sequences = tokenizer.texts_to_sequences(sentences)
print(sequences)
#沒看過的詞不會出現
#[[4, 2, 1, 3], [4, 2, 1, 6], [5, 2, 1, 3], [7, 5, 8, 1, 3, 9, 10]]
上面看到遇到沒看過的字就麻煩了, 有兩個方法可以解決這個問題
- database夠大
- 給予不知道的詞一個特殊的值
另外透過pad_sequences補充到相同長度
#要特別注意<OOV>不能跟真實數據的詞一樣, 否則會混淆
#這邊會自動把沒看過的東西補<OOV>, 然後再轉成數字
tokenizer = Tokenizer(num_words = 100, oov_token="<OOV>")
from tensorflow.keras.preprocessing.sequence import pad_sequences
#會把每一段padding補0到相同長度, 會自動找語句最長的當maxlen
padded = pad_sequences(sequences)
#限制最長的語句長度, post是指把pad補0補在後面, 預設是'pre'
padded = pad_sequences(sequences, padding='post', maxlen=5)
#建立model
#vocab_size是word_index size
#embedding_dim是希望詞向量的維度
#max_length是想要把多少個詞切成一個input
model = tf.keras.Sequential([
tf.keras.layers.Embedding(vocab_size, embedding_dim, input_length=max_length),
tf.keras.layers.Flatten(), #或使用tf.keras.layers.GlobalAveragePooling1D(),
#Global出來的Cell數會比較少, 所以會比較快
tf.keras.layers.Dense(6, activation='relu'),
tf.keras.layers.Dense(1, activation='sigmoid')
])
model.fit(padded,
training_labels_final,
epochs = num_epochs,
validation_data = (testing_padded, testing_labels_final))
e = model.layers[0]
weights = e.get_weights()[0]
print(weights.shape) #10000*16
#原本的word_index的key是word
#現在想把key改成數字 value為word
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
import io
out_v = io.open('vecs.tsv', 'w', encoding='utf-8')
out_m = io.open('meta.tsv', 'w', encoding='utf-8')
#weights等於是words在這些項量上的投影
for word_num in range(1, vocab_size):
word = reverse_word_index[word_num]
embeddings = weights[word_num]
out_m.write(word + "\n")
out_v.write('\t'.join([str(x) for x in embeddings]) + "\n")
out_v.close()
out_m.close()
#https://projector.tensorflow.org/ 可以看project後的分類效果
改成用subword, subword介於詞與字符之間, 能夠比較好的平衡OOV問題
subword有點像我們看英文的字首字尾
- 準備足夠大的訓練database
- 確定期望的subword詞表大小
- 將單詞拆分為字符序並在結尾添加"</w>", 統計單詞頻率
- 統計每一個連續字節對的出現頻率, 選擇最高頻率者合併成新的subword
- 重複第四步, 直到達到第二步設定的subword詞表大小, 或下一個最高頻率的字節對出現頻率為1
因為feature很多, 很難flatten(), 建議是使用GlobalAveragePooling1D
但是subword通常沒有意義, 所以訓練出來也會GG
需要透過RNN, 有Sequence的組起來這些subword才有意義
看起來不用會也沒有關係~~
把重點回到token來看, 這邊要做的是預測下一個字是甚麼, 自動產生文本
tokenizer = Tokenizer()
data = 'In the town of Athy one Jeremy Lanigan \n Battered away ... ...'
#corpus 這邊是把字體都變小寫, 並且每一句拆開
corpus = data.lower().split("\n")
tokenizer.fit_on_texts(corpus)
total_words = len(tokenizer.word_index) + 1 #+1個不在訓練的詞彙
input_sequences = []
#這邊做的動作是把每一句拆成從不同長度
#例如把一整句[4, 2, 66, 8, 67, 68, 69, 70]
#變成 注意下面至少是len >= 2
#[4, 2]
#[4, 2, 66]
#[4, 2, 66, 8]
#...
#[4, 2, 66, 8, 67, 68, 69, 70]
for line in corpus:
token_list = tokenizer.texts_to_sequences([line])[0]
for i in range(1, len(token_list)):
n_gram_sequence = token_list[:i+1]
input_sequences.append(n_gram_sequence)
#找出最長的sub line長度
max_sequence_len = max([len(x) for x in input_sequences])
#把前面都補0, 並轉換成nparray
input_sequences = np.array(pad_sequences(input_sequences, maxlen=max_sequence_len, padding='pre'))
#把每一句subline的最後一個拿去當label, 前面的全部都拿去當input data
xs = input_sequences[:,:-1]
labels = input_sequences[:,-1]
#把label換成one hot encode
ys = tf.keras.utils.to_categorical(labels, num_classes=total_words)
#Bidirectional是雙向, 可以前往後, 也可以前往後互相影響
model = Sequential()
model.add(Embedding(total_words, 64, input_length=max_sequence_len-1))
model.add(Bidirectional(LSTM(20)))
model.add(Dense(total_words, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(xs, ys, epochs=500, verbose=1)
#想要從這句話產生100個詞
seed_text = 'Lauraence went to dublin'
next_words = 100
for _ in range(next_words):
token_list = tokenizer.texts_to_sequences([seed_text])[0]
token_list = pad_sequences([token_list], maxlen=max_sequence_len-1, padding='pre')
predicted = model.predict_classes(token_list, verbose=0)
output_word = ''
#下面是把predicted轉換成詞
#看哪個打中了index跟predicted一樣, 這個詞就是predict的詞
for word, index in tokenizer.word_index.items():
if index == predicted:
output_word = word
break
seed_text += ' ' + output_word
print(seed_text)
這篇先到這邊, 原本想一篇做個結尾的, 但內容有點太多,
後續還有time series model留到下一篇講解